package com.fanshuai.soring.boot.mvc.log;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.filter.AbstractRequestLoggingFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;


@Slf4j
public class SimpleLogFilter extends AbstractRequestLoggingFilter {
    private LogProperties logProperties;

    public SimpleLogFilter(LogProperties logProperties) {
        super();
        setIncludeClientInfo(true);
        setIncludeQueryString(true);
        setIncludeHeaders(true);
        setIncludePayload(true);
        setMaxPayloadLength(logProperties.getMaxPayloadLength());

        this.logProperties = logProperties;
    }

    @Override
    protected boolean shouldLog(HttpServletRequest request) {
        return super.shouldLog(request);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        FilterParams params = new FilterParams(request, response);
        try {
            beforeFilter(params);
            filterChain.doFilter(params.request, params.response);
        } finally {
            afterFilter(params);
        }
    }

    private void beforeFilter(FilterParams params) {
        params.request.setAttribute("startTime", System.currentTimeMillis());

        if (!(params.request instanceof ContentCachingRequestWrapper)) {
            ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(params.request, getMaxPayloadLength());
            params.setRequest(requestWrapper);
        }

        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(params.response);
        responseWrapper.setContentLength(logProperties.getMaxPayloadLength());
        params.setResponse(responseWrapper);

        if (shouldLog(params.request) && !isAsyncDispatch(params.request)) {
            String message = createRequestMessage(params.request);
            beforeRequest(params.request, message);
        }
    }

    private void afterFilter(FilterParams params) throws IOException {
        if (shouldLog(params.request) && !isAsyncStarted(params.request)) {
            String message = createResponse(params.request, params.response);
            afterRequest(params.request, message);

            if (params.response instanceof ContentCachingResponseWrapper) {
                ((ContentCachingResponseWrapper) params.response).copyBodyToResponse();
            }
        }
    }

    @Override
    protected void beforeRequest(HttpServletRequest request, String s) {
        log.info("request={}", s);
    }

    @Override
    protected void afterRequest(HttpServletRequest request, String s) {
        String requestMessage = createRequestMessage(request);
        log.info("request={}, response={}", requestMessage, s);
    }

    private String createRequestMessage(HttpServletRequest request) {
        StringBuilder b = new StringBuilder();
        b.append(request.getMethod()).append(" ").append(request.getRequestURI());

        if (StringUtils.isNotBlank(request.getQueryString())) {
            b.append("?").append(request.getQueryString());
        } else {
            b.append("?");
            Map<String, String[]> params = request.getParameterMap();
            for (String key: params.keySet()) {
                b.append(String.format("%s=%s", key, params.get(key)[0]));
            }
        }

        b.append(" headers= ");
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            b.append(String.format(" %s=%s", headerName, request.getHeader(headerName)));
        }

        String remoteAddr = request.getRemoteAddr();
        b.append(" remoteAddr=").append(remoteAddr);
        b.append(" body=").append(getMessagePayload(request));
        return b.toString();
    }

    private String createResponse(HttpServletRequest request, HttpServletResponse response)  {
        if (response instanceof ContentCachingResponseWrapper) {
            ContentCachingResponseWrapper responseWrapper = (ContentCachingResponseWrapper) response;

            byte[] body = responseWrapper.getContentAsByteArray();
            int len = Math.min(body.length, getMaxPayloadLength());
            String s = new String(body, 0, len, StandardCharsets.UTF_8);

            StringBuilder b = new StringBuilder();
            b.append("body=").append(s);

            long start = (long) request.getAttribute("startTime");
            b.append(" cost=").append(System.currentTimeMillis() - start).append("ms");
            return b.toString();
        }

        return "";
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class FilterParams {
        HttpServletRequest request;
        HttpServletResponse response;
    }
}
